{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "dae5b41a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OpenWeather API Key: 1564bd59e86f981289a646fb2c421a63\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "from dotenv import load_dotenv \n",
    "load_dotenv(override=True)\n",
    "\n",
    "OPENWEATHER_API_KEY = os.getenv(\"OPENWEATHER_API_KEY\")\n",
    "print(\"OpenWeather API Key:\", OPENWEATHER_API_KEY)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "bd5608f6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'{\"coord\": {\"lon\": 116.3972, \"lat\": 39.9075}, \"weather\": [{\"id\": 804, \"main\": \"Clouds\", \"description\": \"\\\\u9634\\\\uff0c\\\\u591a\\\\u4e91\", \"icon\": \"04d\"}], \"base\": \"stations\", \"main\": {\"temp\": 29.49, \"feels_like\": 29.02, \"temp_min\": 29.49, \"temp_max\": 29.49, \"pressure\": 1007, \"humidity\": 39, \"sea_level\": 1007, \"grnd_level\": 1002}, \"visibility\": 10000, \"wind\": {\"speed\": 1.07, \"deg\": 163, \"gust\": 1.68}, \"clouds\": {\"all\": 100}, \"dt\": 1750908365, \"sys\": {\"country\": \"CN\", \"sunrise\": 1750884439, \"sunset\": 1750938415}, \"timezone\": 28800, \"id\": 1816670, \"name\": \"Beijing\", \"cod\": 200}'"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import requests,json\n",
    "def get_weather(loc):\n",
    "    \"\"\"\n",
    "    查询即时天气函数\n",
    "    :param loc: 必要参数，字符串类型，用于表示查询天气的具体城市名称，\\\n",
    "    注意，中国的城市需要用对应城市的英文名称代替，例如如果需要查询北京市天气，则loc参数需要输入'Beijing'；\n",
    "    :return：OpenWeather API查询即时天气的结果，具体URL请求地址为：https://api.openweathermap.org/data/2.5/weather\\\n",
    "    返回结果对象类型为解析之后的JSON格式对象，并用字符串形式进行表示，其中包含了全部重要的天气信息\n",
    "    \"\"\"\n",
    "    # Step 1.构建请求\n",
    "    url = \"https://api.openweathermap.org/data/2.5/weather\"\n",
    "\n",
    "    # Step 2.设置查询参数\n",
    "    params = {\n",
    "        \"q\": loc,               \n",
    "        \"appid\": OPENWEATHER_API_KEY,    # 输入API key\n",
    "        \"units\": \"metric\",            # 使用摄氏度而不是华氏度\n",
    "        \"lang\":\"zh_cn\"                # 输出语言为简体中文\n",
    "    }\n",
    "\n",
    "    # Step 3.发送GET请求\n",
    "    response = requests.get(url, params=params)\n",
    "    \n",
    "    # Step 4.解析响应\n",
    "    data = response.json()\n",
    "    return json.dumps(data)\n",
    "# Example usage\n",
    "get_weather(\"Beijing\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "52b60687",
   "metadata": {},
   "source": [
    "## 将其封装为LangChain能够识别的外部函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "cbec5c2c",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.tools import tool\n",
    "\n",
    "@tool\n",
    "def get_weather(loc):\n",
    "    \"\"\"\n",
    "    查询即时天气函数\n",
    "    :param loc: 必要参数，字符串类型，用于表示查询天气的具体城市名称，\\\n",
    "    注意，中国的城市需要用对应城市的英文名称代替，例如如果需要查询北京市天气，则loc参数需要输入'Beijing'；\n",
    "    :return：OpenWeather API查询即时天气的结果，具体URL请求地址为：https://api.openweathermap.org/data/2.5/weather\\\n",
    "    返回结果对象类型为解析之后的JSON格式对象，并用字符串形式进行表示，其中包含了全部重要的天气信息\n",
    "    \"\"\"\n",
    "    # Step 1.构建请求\n",
    "    url = \"https://api.openweathermap.org/data/2.5/weather\"\n",
    "\n",
    "    # Step 2.设置查询参数\n",
    "    params = {\n",
    "        \"q\": loc,               \n",
    "        \"appid\": os.getenv(\"OPENWEATHER_API_KEY\"),    # 输入API key\n",
    "        \"units\": \"metric\",            # 使用摄氏度而不是华氏度\n",
    "        \"lang\":\"zh_cn\"                # 输出语言为简体中文\n",
    "    }\n",
    "\n",
    "    # Step 3.发送GET请求\n",
    "    response = requests.get(url, params=params)\n",
    "    \n",
    "    # Step 4.解析响应\n",
    "    data = response.json()\n",
    "    return json.dumps(data)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e64fbb90",
   "metadata": {},
   "source": [
    "- 测试"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "35a05718",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "get_weather\n",
      "查询即时天气函数\n",
      ":param loc: 必要参数，字符串类型，用于表示查询天气的具体城市名称，    注意，中国的城市需要用对应城市的英文名称代替，例如如果需要查询北京市天气，则loc参数需要输入'Beijing'；\n",
      ":return：OpenWeather API查询即时天气的结果，具体URL请求地址为：https://api.openweathermap.org/data/2.5/weather    返回结果对象类型为解析之后的JSON格式对象，并用字符串形式进行表示，其中包含了全部重要的天气信息\n",
      "{'loc': {'title': 'Loc'}}\n"
     ]
    }
   ],
   "source": [
    "print(get_weather.name)\n",
    "print(get_weather.description)\n",
    "print(get_weather.args)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13d83486",
   "metadata": {},
   "source": [
    "## 继续使用DeepSeek模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "116cc47f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "content='' additional_kwargs={'tool_calls': [{'id': 'call_0_256fcbc4-7443-4625-98a8-3ff06872b432', 'function': {'arguments': '{\"loc\":\"Beijing\"}', 'name': 'get_weather'}, 'type': 'function', 'index': 0}], 'refusal': None} response_metadata={'token_usage': {'completion_tokens': 19, 'prompt_tokens': 203, 'total_tokens': 222, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 192}, 'prompt_cache_hit_tokens': 192, 'prompt_cache_miss_tokens': 11}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0623_fp8_kvcache', 'id': 'a8688cee-5816-4d38-be0d-989f2f5d19a3', 'service_tier': None, 'finish_reason': 'tool_calls', 'logprobs': None} id='run--46bc124a-ff25-41aa-b2bc-4f454a82c03a-0' tool_calls=[{'name': 'get_weather', 'args': {'loc': 'Beijing'}, 'id': 'call_0_256fcbc4-7443-4625-98a8-3ff06872b432', 'type': 'tool_call'}] usage_metadata={'input_tokens': 203, 'output_tokens': 19, 'total_tokens': 222, 'input_token_details': {'cache_read': 192}, 'output_token_details': {}}\n"
     ]
    }
   ],
   "source": [
    "from langchain.chat_models import init_chat_model\n",
    "\n",
    "# 初始化模型\n",
    "model = init_chat_model(\"deepseek-chat\", model_provider=\"deepseek\")\n",
    "\n",
    "# 定义 天气查询 工具函数\n",
    "tools = [get_weather]\n",
    "\n",
    "# 将工具绑定到模型\n",
    "llm_with_tools = model.bind_tools(tools)\n",
    "\n",
    "response = llm_with_tools.invoke(\"你好， 请问北京的天气怎么样？\")\n",
    "\n",
    "print(response)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "bcd5bc0b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "北京当前阴天多云，气温29.5°C，体感温度29°C，湿度39%，风速1.07米/秒。\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.output_parsers.openai_tools import JsonOutputKeyToolsParser\n",
    "from langchain.prompts import PromptTemplate\n",
    "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
    "from langchain_core.output_parsers import StrOutputParser\n",
    "# 使用JsonOutputKeyToolsParser解析工具调用的结果\n",
    "# 只解析第一个工具调用的结果\n",
    "parser = JsonOutputKeyToolsParser(key_name=get_weather.name, first_tool_only=True)\n",
    "# 将模型和解析器组合成一个链\n",
    "llm_chain = llm_with_tools | parser\n",
    "# system = f\"\"\"\n",
    "# 你可以访问一个名为 `df` 的 pandas 数据框，你可以使用df.head().to_markdown() 查看数据集的基本信息， \\\n",
    "# 请根据用户提出的问题，编写 Python 代码来回答。只返回代码，不返回其他内容。只允许使用 pandas 和内置库。\n",
    "# \"\"\"\n",
    "prompt = ChatPromptTemplate([\n",
    "    (\"system\", \"你是天气助手，请根据用户的问题，给出相应的天气信息\"),\n",
    "    (\"user\", \"{question}\")\n",
    "])\n",
    "# Prompt 模板\n",
    "output_prompt = PromptTemplate.from_template(\n",
    "    \"\"\"你将收到一段 JSON 格式的天气数据，请用简洁自然的方式将其转述给用户。\n",
    "以下是天气 JSON 数据：\n",
    "\n",
    "```json\n",
    "{weather_json}\n",
    "```\n",
    "请将其转换为中文天气描述，例如：\n",
    "“北京当前天气晴，气温为 23°C，湿度 58%，风速 2.1 米/秒。”\n",
    "只返回一句话描述，不要其他说明或解释。\"\"\"\n",
    ")\n",
    "# 提问\n",
    "get_weather_chain = prompt | llm_with_tools | parser | get_weather\n",
    "# 将输出格式化为字符串\n",
    "output_chain = output_prompt | model | StrOutputParser()\n",
    "# 将两个链组合成一个完整的链\n",
    "full_chain = get_weather_chain | output_chain\n",
    "response = full_chain.invoke(\"请问北京今天的天气如何？\")\n",
    "print(response)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "99340bc7",
   "metadata": {},
   "source": [
    "## create_tool_calling_agent"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "de557fe2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
      "\u001b[32;1m\u001b[1;3m\n",
      "Invoking: `get_weather` with `{'loc': 'Beijing'}`\n",
      "\n",
      "\n",
      "\u001b[0m\u001b[36;1m\u001b[1;3m{\"coord\": {\"lon\": 116.3972, \"lat\": 39.9075}, \"weather\": [{\"id\": 804, \"main\": \"Clouds\", \"description\": \"\\u9634\\uff0c\\u591a\\u4e91\", \"icon\": \"04d\"}], \"base\": \"stations\", \"main\": {\"temp\": 29.49, \"feels_like\": 29.02, \"temp_min\": 29.49, \"temp_max\": 29.49, \"pressure\": 1007, \"humidity\": 39, \"sea_level\": 1007, \"grnd_level\": 1002}, \"visibility\": 10000, \"wind\": {\"speed\": 1.07, \"deg\": 163, \"gust\": 1.68}, \"clouds\": {\"all\": 100}, \"dt\": 1750908423, \"sys\": {\"country\": \"CN\", \"sunrise\": 1750884439, \"sunset\": 1750938415}, \"timezone\": 28800, \"id\": 1816670, \"name\": \"Beijing\", \"cod\": 200}\u001b[0m\u001b[32;1m\u001b[1;3m今天北京的天气是多云，阴天。当前温度为29.49°C，体感温度约为29.02°C。湿度为39%，风速为1.07米/秒，风向为163度。能见度为10公里。\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n",
      "{'input': '请问今天北京的天气怎么样？', 'output': '今天北京的天气是多云，阴天。当前温度为29.49°C，体感温度约为29.02°C。湿度为39%，风速为1.07米/秒，风向为163度。能见度为10公里。'}\n"
     ]
    }
   ],
   "source": [
    "from langchain.agents import create_tool_calling_agent, tool\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "\n",
    "#定义工具\n",
    "tools = [get_weather]\n",
    "\n",
    "# 构建提示模版\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\"system\", \"你是天气助手，请根据用户的问题，给出相应的天气信息\"),\n",
    "        (\"human\", \"{input}\"),\n",
    "        (\"placeholder\", \"{agent_scratchpad}\"),\n",
    "    ]\n",
    ")\n",
    "\n",
    "# 初始化模型\n",
    "model = init_chat_model(\"deepseek-chat\", model_provider=\"deepseek\")\n",
    "\n",
    "# 直接使用`create_tool_calling_agent`创建代理\n",
    "agent = create_tool_calling_agent(model, tools, prompt)\n",
    "# Agent 是 LangChain 中用于处理工具调用的类\n",
    "# 它可以根据用户输入和工具定义，自动选择合适的工具进行调用\n",
    "# 将代理和工具组合成一个 AgentExecutor\n",
    "# AgentExecutor 是 LangChain 中用于执行代理的类\n",
    "from langchain.agents import AgentExecutor\n",
    "agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)\n",
    "\n",
    "\n",
    "response = agent_executor.invoke({\"input\": \"请问今天北京的天气怎么样？\"})\n",
    "print(response)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "59eae4ff",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
      "\u001b[32;1m\u001b[1;3m\n",
      "Invoking: `get_weather` with `{'loc': 'Beijing'}`\n",
      "\n",
      "\n",
      "\u001b[0m\u001b[36;1m\u001b[1;3m{\"coord\": {\"lon\": 116.3972, \"lat\": 39.9075}, \"weather\": [{\"id\": 804, \"main\": \"Clouds\", \"description\": \"\\u9634\\uff0c\\u591a\\u4e91\", \"icon\": \"04d\"}], \"base\": \"stations\", \"main\": {\"temp\": 29.49, \"feels_like\": 29.02, \"temp_min\": 29.49, \"temp_max\": 29.49, \"pressure\": 1007, \"humidity\": 39, \"sea_level\": 1007, \"grnd_level\": 1002}, \"visibility\": 10000, \"wind\": {\"speed\": 1.07, \"deg\": 163, \"gust\": 1.68}, \"clouds\": {\"all\": 100}, \"dt\": 1750908423, \"sys\": {\"country\": \"CN\", \"sunrise\": 1750884439, \"sunset\": 1750938415}, \"timezone\": 28800, \"id\": 1816670, \"name\": \"Beijing\", \"cod\": 200}\u001b[0m\u001b[32;1m\u001b[1;3m\n",
      "Invoking: `get_weather` with `{'loc': 'Hangzhou'}`\n",
      "\n",
      "\n",
      "\u001b[0m\u001b[36;1m\u001b[1;3m{\"coord\": {\"lon\": 120.1614, \"lat\": 30.2937}, \"weather\": [{\"id\": 803, \"main\": \"Clouds\", \"description\": \"\\u591a\\u4e91\", \"icon\": \"04d\"}], \"base\": \"stations\", \"main\": {\"temp\": 33.68, \"feels_like\": 40.68, \"temp_min\": 33.68, \"temp_max\": 33.68, \"pressure\": 1008, \"humidity\": 67, \"sea_level\": 1008, \"grnd_level\": 1005}, \"visibility\": 10000, \"wind\": {\"speed\": 2.13, \"deg\": 214, \"gust\": 3.43}, \"clouds\": {\"all\": 73}, \"dt\": 1750908314, \"sys\": {\"country\": \"CN\", \"sunrise\": 1750885155, \"sunset\": 1750935893}, \"timezone\": 28800, \"id\": 1808926, \"name\": \"Hangzhou\", \"cod\": 200}\u001b[0m\u001b[32;1m\u001b[1;3m今天北京的天气是多云，气温为29.49°C，体感温度为29.02°C，湿度为39%。\n",
      "\n",
      "杭州的天气也是多云，气温为33.68°C，体感温度为40.68°C，湿度为67%。\n",
      "\n",
      "比较来看，**杭州比北京更热**，气温和体感温度都更高。\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "# 一次性发起了同一个外部函数的两次调用请求，并依次获得了北京和杭州两个城市的天气。这就是一次标准的parallel_function_call \n",
    "# 并联示例\n",
    "response = agent_executor.invoke({\"input\": \"请问今天北京和杭州的天气怎么样，哪个城市更热？\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "38710e09",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 此时我们再定义一个write_file函数，用于将“文本写入本地”：\n",
    "@tool\n",
    "def write_file(content):\n",
    "    \"\"\"\n",
    "    将指定内容写入本地文件。\n",
    "    :param content: 必要参数，字符串类型，用于表示需要写入文档的具体内容。\n",
    "    :return：是否成功写入\n",
    "    \"\"\"\n",
    "    \n",
    "    return \"已成功写入本地文件。\"\n",
    "\n",
    "from langchain.agents import AgentExecutor, create_tool_calling_agent, tool\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "\n",
    "\n",
    "tools = [get_weather, write_file]\n",
    "\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\"system\", \"你是天气助手，请根据用户的问题，给出相应的天气信息，如果用户需要将查询结果写入文件，请使用write_file工具\"),\n",
    "        (\"human\", \"{input}\"),\n",
    "        (\"placeholder\", \"{agent_scratchpad}\"),\n",
    "    ]\n",
    ")\n",
    "\n",
    "# 初始化模型\n",
    "model = init_chat_model(\"deepseek-chat\", model_provider=\"deepseek\")\n",
    "\n",
    "agent = create_tool_calling_agent(model, tools, prompt)\n",
    "agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)\n",
    "\n",
    "#通过中间过程信息的打印，我们能够看到在一次交互过程中依次调用的get_weather查询到北京和杭州的天气，\n",
    "# 然后又将结果写入到本地的文件中。这就是一个非常典型的串行工具调用的流程\n",
    "agent_executor.invoke({\"input\": \"查一下北京和杭州现在的温度，并将结果写入本地的文件中。\"})"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
